-- Helicopter Alife Simulator -- Alundaio -- -- -- current action by id -- STAY_ON_TARGET = 0 -- REACH_TARGET = 1 local STAY_POINT_IDLE = 6800 -- in-game seconds -------------------------------------------------------------------------------------------------------------- local alife, pairs, math = alife, pairs, math -------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------- -- PRIVATE -------------------------------------------------------------------------------------------------------------- local function is_target_reached(se_obj,se_target) local gvid = se_obj.m_game_vertex_id local target_gvid = se_target.m_game_vertex_id local gg = game_graph() if gg:vertex(gvid):level_id() ~= gg:vertex(target_gvid):level_id() then return false end if (se_target:clsid() == clsid.online_offline_group_s) then return true end local dist = distance_2d_sqr(se_obj.position,se_target.position) return dist <= 25 or se_obj.always_arrived or se_target.arrive_dist and dist < se_target.arrive_dist or false end local function evaluate_prior_by_dist(se_target, se_obj) local dist = utils_obj.graph_distance(se_target.m_game_vertex_id, se_obj.m_game_vertex_id) if dist < 1 then dist = 1 end return 1 + 1/dist end local function evaluate_prior(se_target, se_obj) if (se_target:clsid() == clsid.script_actor) then if not (simulation_objects.is_on_the_same_level(se_target, se_obj)) then return 0 end elseif (se_target:clsid() == clsid.online_offline_group_s) then local sim = alife() if (not simulation_objects.is_on_the_same_level(sim:actor(), se_obj)) then return 0 end if (xr_combat_ignore.safe_zone_npcs[se_target.id]) then return 0 end elseif (se_target:clsid() == clsid.smart_terrain) then if not (simulation_objects.is_on_the_same_level(se_target, se_obj)) then return 0 end end return 5 end local priority_tasks = {} local function get_smart_terrain_target(se_obj) local size_t = 0 local object_registry = simulation_objects.object_registry local is_available = simulation_objects.available_by_id for index=1,simulation_objects.object_registry_size do local se_target = object_registry[index] if (is_available[se_target.id]) then local curr_prior = evaluate_prior(se_target,se_obj) if (curr_prior > 0 and se_target.props and se_target.props[se_obj.player_id] and se_target.props[se_obj.player_id] > 0) then -- Prioritize 5 potential targets if (size_t < 5) then size_t = size_t + 1 priority_tasks[size_t] = {se_target,curr_prior} elseif (curr_prior > priority_tasks[size_t][2]) then for i=1,size_t do if (curr_prior > priority_tasks[i][2]) then priority_tasks[i][2] = curr_prior priority_tasks[i][1] = se_target break end end end end end end if (size_t > 0) then local target = priority_tasks[math.random(size_t)][1] iempty_table(priority_tasks) -- It is better to reuse table to avoid GC return target end local smart = se_obj.m_smart_terrain_id and se_obj.m_smart_terrain_id ~= 65535 and alife_object(se_obj.m_smart_terrain_id) return smart and not smart.disabled and smart end local function try_to_reach_target(heli,se_obj,heliObject,se_target) heli = heli or db.storage[se_obj.id] and db.storage[se_obj.id].object heliObject = heliObject or heli and heli:get_helicopter() if not (heliObject) then abort("heli_alife.try_to_reach_target: heliObject is nil! IMPOSSIBLE!") return end --heliObject:TurnEngineSound(ui_options.get("alife/general/heli_engine_sound")) local vel = db.storage[se_obj.id].combat.max_velocity or 30 heliObject:SetMaxVelocity(vel) local gg = game_graph() local gvid = se_obj.m_game_vertex_id local target_gvid = se_target.m_game_vertex_id local sim = alife() if gg:vertex(gvid):level_id() == gg:vertex(target_gvid):level_id() then if (heliObject) then -- Climb to a safe altitude before moving to next target local pos = heli:position() if (pos.y+10 < se_target.position.y+70) then heliObject:SetDestPosition(vector():set(pos.x,se_target.position.y+70,pos.z)) return end if (se_target:clsid() == clsid.smart_terrain) then heliObject:SetDestPosition(vector():set(se_target.position.x,se_target.position.y+70,se_target.position.z)) else local center_pos = vector():set(se_target.position) center_pos.y = center_pos.y + 60 heliObject:SetMaxVelocity(vel) heliObject:GoPatrolByRoundPath(center_pos,50,true) heliObject:LookAtPoint(se_target.position, true ) end end end end -------------------------------------------------------------------------------------------------------------- -- PUBLIC -------------------------------------------------------------------------------------------------------------- function force_target_destination(id,target_id) if (xr_conditions.surge_started()) then return end --utils_data.debug_write("heli_alife: force_target_destination") local sim = alife() local se_obj = sim:object(id) if not (se_obj) then return end local se_target = sim:object(target_id) if not (se_target) then return end -- Only accept help signals from same community if not (se_obj.community == se_target.player_id) then return end local gg = game_graph() if gg:vertex(se_obj.m_game_vertex_id):level_id() == gg:vertex(se_target.m_game_vertex_id):level_id() then local smart = se_obj.m_smart_terrain_id and se_obj.m_smart_terrain_id ~= 65535 and sim:object(se_obj.m_smart_terrain_id) if (smart) then smart:unregister_npc(se_obj) end se_obj.help_id = se_target.id se_obj.assigned_target_id = se_target.id se_obj.current_target_id = se_obj.assigned_target_id se_obj.current_action = 0 se_obj.stay_point_idle = game.get_game_time() end end function update(heli,se_obj,heliObject) if not (db.actor) then return end if not (heliObject) then return end -- Check if in combat local st = db.storage[se_obj.id] if (st and st.combat and st.combat:update()) then return end --utils_data.debug_write("heli_alife: update start") local sim = alife() -- se_obj position never updates to game_object; force it for evaluation purposes (Note: resets if object goes offline) se_obj.position = heli:position() se_obj.dist_to_actor = distance_2d_sqr(se_obj.position,db.actor:position()) -- debug --if not (se_obj.spot) then --level.map_add_object_spot(se_obj.id, "storyline_task_location", "helicopter") --se_obj.spot = true --end local gg = game_graph() local level_name = sim:level_name(gg:vertex(se_obj.m_game_vertex_id):level_id()) if (xr_conditions.surge_started()) then se_obj.help_id = nil end if (se_obj.current_action) then -- Evaluate current assigned target; make sure it is still valid local se_target = se_obj.assigned_target_id and simulation_objects.available_by_id[se_obj.assigned_target_id] and simulation_objects.get_server_entity(se_obj.assigned_target_id) if (se_target) then -- If current action is not finished, then return if (se_obj.current_action == 0) then if not (se_obj.stay_point_idle) then --utils_data.debug_write("heli_alife: update end 1") se_obj.current_action = nil return end if (game.get_game_time():diffSec(se_obj.stay_point_idle) < STAY_POINT_IDLE) then if (se_target:clsid() == clsid.online_offline_group_s) then update_heli_movement(heli,se_obj,heliObject,se_target) elseif (se_target:clsid() == clsid.smart_terrain) then if not (se_obj.m_smart_terrain_id) then se_obj.current_action = nil return end end --utils_data.debug_write("heli_alife: update end 3") return end if (se_target:clsid() == clsid.smart_terrain) then se_target:unregister_npc(se_obj) end se_obj.current_action = nil se_obj.stay_point_idle = nil se_obj.current_target_id = nil elseif (se_obj.current_action == 1) then if not (xr_conditions.surge_started()) then if not (is_target_reached(se_obj,se_target)) then try_to_reach_target(heli,se_obj,heliObject,se_target) --utils_data.debug_write("heli_alife: update end 4") return end end if (se_target:clsid() == clsid.smart_terrain) then se_target:register_npc(se_obj) end se_obj.current_target_id = se_obj.assigned_target_id se_obj.current_action = 0 se_obj.stay_point_idle = game.get_game_time() --utils_data.debug_write("heli_alife: update end 5") return end end end -- Ensure the exit of spawned-by smart local smart = se_obj.m_smart_terrain_id and sim:object(se_obj.m_smart_terrain_id) if (smart) then smart:unregister_npc(se_obj) end heliObject:TurnEngineSound(ui_options.get("alife/general/heli_engine_sound")) se_obj.help_id = nil -- Find a new target local target = get_smart_terrain_target(se_obj) if (target) then se_obj.assigned_target_id = target.id se_obj.current_action = 1 else se_obj.current_target_id = se_obj.assigned_target_id se_obj.current_action = 0 se_obj.stay_point_idle = game.get_game_time() end --utils_data.debug_write("heli_alife: update end") end function update_heli_movement(heli,se_obj,heliObject,se_target) --utils_data.debug_write("heli_alife: update_heli_movement start") local tg = time_global() local st = db.storage[se_obj.id] if not (st) then return end if (st.change_speed_time and tg < st.change_speed_time) then return end st.change_speed_time = tg + 15000 local pos = se_target.position if (se_target:clsid() == clsid.online_offline_group_s) then local npc = db.storage[se_target:commander_id()] and db.storage[se_target:commander_id()].object if (npc) then pos = npc:position() end end local center_pos = vector():set(pos) center_pos.y = level.get_bounding_volume().max.y heliObject:GoPatrolByRoundPath(center_pos, 70, random_choice(true,false)) heliObject:LookAtPoint(center_pos, true) heliObject:SetMaxVelocity(12) heliObject:SetSpeedInDestPoint(12) --utils_data.debug_write("heli_alife: update_heli_movement end") end